In [1]:
import pandas as pd
import matplotlib.pyplot as plt
import json
from datetime import datetime

# === PARAMETERS ===
FICHIER_CSV = "all_data_enriched.csv"
SUMMARY_PATH = "summary.json"
MAX_POINTS = 1000

# === LOAD DATA ===
df = pd.read_csv(FICHIER_CSV, low_memory=False)

# === LOAD SUMMARY ===
with open(SUMMARY_PATH, "r") as f:
    summary = json.load(f)

# === UTILITY ===
def filter_interval(df, start, end):
    return df[(df["SecondsSince1970"] >= start) & (df["SecondsSince1970"] <= end)].reset_index(drop=True)

def format_timestamp(ts):
    return datetime.utcfromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]

def normalize_angle(angle_series):
    return ((angle_series + 180) % 360) - 180


# === PLOTTING FUNCTION ===
def plot_senseboard_report(run_name, interval, data):
    if len(data) > MAX_POINTS:
        data = data.sample(n=MAX_POINTS).sort_values("ISODateTimeUTC")

    time = pd.to_datetime(data["ISODateTimeUTC"]).dt.tz_localize(None)
    sb_time = pd.to_datetime(data["Timestamp GPS"]).dt.tz_localize(None)

    wind_side = "Upwind" if interval["avg TWA boat2"] > 0 else "Downwind"
    opponent = interval["boat1_name"] if interval["boat2_name"] == "SenseBoard" else interval["boat2_name"]
    start_str = format_timestamp(interval["start_time"])
    end_str = format_timestamp(interval["end_time"])

    fig, axes = plt.subplots(2, 5, figsize=(24, 8))
    fig.suptitle(
        f"Run {run_name}, Interval 1: {wind_side} from {start_str} to {end_str}\n"
        f"SenseBoard (vs {opponent})",
        fontsize=18
    )

    # Row 1
    axes[0, 0].plot(time, data["TWS"])
    axes[0, 0].set_title("True Wind Speed (TWS)")
    axes[0, 1].plot(time, data["TWD"])
    axes[0, 1].set_title("True Wind Direction (TWD)")
    axes[0, 2].plot(data["Lon"], data["Lat"], label="Vakaros", color="blue")
    axes[0, 2].plot(data["Longitude"], data["Latitude"], label="SenseBoard", color="orange", linestyle="--")
    axes[0, 2].set_title("GPS Track")
    axes[0, 2].legend()
    axes[0, 3].plot(time, data["SOG"])
    axes[0, 3].set_title("Speed Over Ground (SOG)")
    axes[0, 4].plot(time, data["COG"])
    axes[0, 4].set_title("Course Over Ground (COG)")

    # Row 2
    axes[1, 0].plot(time, data["Heel"], label="Vakaros", color="blue")
    axes[1, 0].plot(sb_time, normalize_angle(data["Euler_X (deg)"]), label="SenseBoard", color="orange", linestyle="--")

    #axes[1, 0].plot(sb_time, data["Euler_X (deg)"], label="SenseBoard", color="orange", linestyle="--")
    axes[1, 0].set_title("Heel Angle")
    axes[1, 0].legend()
    axes[1, 1].plot(time, data["Trim"], label="Vakaros", color="blue")
    axes[1, 1].plot(sb_time, data["Euler_Y (deg)"], label="SenseBoard", color="orange", linestyle="--")
    axes[1, 1].set_title("Pitch Angle")
    axes[1, 1].legend()
    axes[1, 2].plot(time, data["F_front"])
    axes[1, 2].set_title("Front Force")
    axes[1, 3].plot(time, data["F_back"])
    axes[1, 3].set_title("Back Force")
    axes[1, 4].axis("off")

    # Set x-labels and rotation only for relevant plots
    for i in [(0, 0), (0, 1), (0, 3), (0, 4), (1, 0), (1, 1), (1, 2), (1, 3)]:
        axes[i].set_xlabel("Time")
        axes[i].tick_params(axis='x', rotation=30)

    # Set proper labels for GPS
    axes[0, 2].set_xlabel("Longitude")
    axes[0, 2].set_ylabel("Latitude")

    plt.tight_layout(rect=[0, 0.03, 1, 0.95])
    plt.show()

# === MAIN LOOP ===
for entry in summary:
    run = entry["run"]
    intervals = entry.get("intervals", [])
    if len(intervals) < 1:
        continue

    interval = intervals[0]
    if "SenseBoard" not in [interval["boat1_name"], interval["boat2_name"]]:
        continue
    
    run_df = df[df["run"] == run]
    sb_df = run_df[run_df["boat_name"] == "SenseBoard"]
    if sb_df.empty:
        print(f"[SKIPPED] No SenseBoard data for run {run}")
        continue

    filtered_data = filter_interval(sb_df, interval["start_time"], interval["end_time"])
    if filtered_data.empty:
        print(f"[SKIPPED] Empty interval data for run {run}")
        continue

    plot_senseboard_report(run, interval, filtered_data)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [2]:
# === DOWNWIND LOOP ===
for entry in summary:
    run = entry["run"]
    intervals = entry.get("intervals", [])
    if len(intervals) < 2:
        continue  # Skip if no downwind interval

    interval = intervals[1]  # Always use second interval (Downwind)
    if interval["boat2_name"] != "SenseBoard":
        continue

    run_df = df[df["run"] == run]
    sb_df = run_df[run_df["boat_name"] == "SenseBoard"]
    if sb_df.empty:
        print(f"[SKIPPED] No SenseBoard data for run {run}")
        continue

    filtered_data = filter_interval(sb_df, interval["start_time"], interval["end_time"])
    if filtered_data.empty:
        print(f"[SKIPPED] Empty interval data for run {run}")
        continue

    plot_senseboard_report(run, interval, filtered_data)
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [ ]: